package jp.sourceforge.talisman.mds;

/*
 * $Id: Table.java,v 1.1 2009/09/12 05:06:20 weiwei Exp $
 */

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * This class represents an input data set of {@link MdsMethod
 * <code>MdsMethod</code>}.
 * 
 * @author Haruaki Tamada
 * @version $Revision: 1.1 $ 
 */
public class Table<T> implements Iterable<Table.Entry<T>>{
    private Map<T, Map<T, Number>> table = new LinkedHashMap<T, Map<T, Number>>();
    private Set<T> nameset = null;

    public void addValue(T nameX, T nameY, Number value){
        nameset = null;
        Map<T, Number> valueMap = table.get(nameX);
        if(valueMap == null){
            valueMap = new LinkedHashMap<T, Number>();
            table.put(nameX, valueMap);
        }
        valueMap.put(nameY, value);
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();
        Set<T> headerSet = getKeySet();

        for(T v: headerSet){
            sb.append(", ").append(v);
        }
        for(T x: headerSet){
            sb.append(System.getProperty("line.separator"));
            sb.append(x);
            for(T y: headerSet){
                if(x == y){
                    break;
                }
                sb.append(", ").append(getValue(x, y));
            }
        }

        return new String(sb);
    }

    /**
     * 
     * @return key set which is unmodifiable.
     */
    public Set<T> getKeySet(){
        if(nameset == null){
            nameset = constructNameset();
        }
        return Collections.unmodifiableSet(nameset);
    }

    public T[] getKeys(T[] array){
        if(nameset == null){
            nameset = constructNameset();
        }
        return nameset.toArray(array);
    }

    public Object[] getKeys(){
        if(nameset == null){
            nameset = constructNameset();
        }
        return nameset.toArray();
    }

    public int getKeySize(){
        if(nameset == null){
            nameset = constructNameset();
        }
        return nameset.size();
    }

    public Number getValue(T nameX, T nameY){
        return getValueImpl(nameX, nameY);
    }

    public boolean isValid(){
        /* This routine checks that every pairs have a value. */
        Object[] names = getKeys();
        for(int i = 0; i < names.length; i++){
            for(int j = i + 1; j < names.length; j++){
                if(getValueImpl(names[i], names[j]) == null){
                    return false;
                }
            }
        }
        return true;
    }

    public void removeValue(T nameX, T nameY){
        Number num = getValueSimply(nameX, nameY);
        if(num == null){
            T tmp = nameX;
            nameX = nameY;
            nameY = tmp;

            num = getValueSimply(nameX, nameY);
        }
        if(num != null){
            nameset = null;
            Map<T, Number> valueMap = table.get(nameX);
            valueMap.remove(nameY);
            if(valueMap.size() == 0){
                table.remove(nameX);
            }
        }
    }

    /**
     * returned iterator does not support remove method which throw UnsupportedOperationException.
     */
    public Iterator<Entry<T>> iterator(){
        return new TableIterator<T>(Collections.unmodifiableMap(table));
    }

    public void removeRow(T nameX){
        table.remove(nameX);
        nameset = null;
    }

    public void removeColumn(T nameY){
        for(Map<T, Number> valueMap: table.values()){
            valueMap.remove(nameY);
        }
        nameset = null;
    }

    public int getRowCount(){
        return table.size();
    }

    public int getColumnCount(T nameX){
        Map<T, Number> valueMap = table.get(nameX);
        int size = 0;
        if(valueMap != null){
            size = valueMap.size();
        }
        return size;
    }

    public int getMaxColumnCount(){
        int size = 0;
        for(Map<T, Number> valueMap: table.values()){
            if(size < valueMap.size()){
                size = valueMap.size();
            }
        }
        return size;
    }

    private Number getValueImpl(Object nameX, Object nameY){
        Number value = getValueSimply(nameX, nameY);
        if(value == null){
            value = getValueSimply(nameY, nameX);
        }
        return value;
    }

    private Number getValueSimply(Object nameX, Object nameY){
        Map<T, Number> valueMap = table.get(nameX);
        if(valueMap != null){
            return valueMap.get(nameY);
        }
        return null;
    }

    private Set<T> constructNameset(){
        Set<T> nameset = new LinkedHashSet<T>();
        for(Map.Entry<T, Map<T, Number>> entry: table.entrySet()){
            nameset.add(entry.getKey());
            for(Map.Entry<T, Number> subentry: entry.getValue().entrySet()){
                nameset.add(subentry.getKey());
            }
        }
        return nameset;
    }

    public static class Entry<T>{
        T x;
        T y;
        Number value;

        public Entry(T x, T y, Number value){
            this.x = x;
            this.y = y;
            this.value = value;
        }

        public T getX(){
            return x;
        }

        public T getY(){
            return y;
        }

        public Number getValue(){
            return value;
        }
    }

    private static class TableIterator<T> implements Iterator<Entry<T>>{
        private Iterator<Map.Entry<T, Map<T, Number>>> iteratorX;
        private Iterator<Map.Entry<T, Number>> iteratorY;
        private Table.Entry<T> next;
        private T x;

        public TableIterator(Map<T, Map<T, Number>> table){
            iteratorX = table.entrySet().iterator();
            next = getNext();
        }

        public boolean hasNext(){
            return next != null;
        }

        public Entry<T> next(){
            if(!hasNext()){
                throw new NoSuchElementException();
            }
            Table.Entry<T> n = next;
            next = getNext();
            return n;
        }

        public void remove(){
            throw new UnsupportedOperationException();
        }

        private Table.Entry<T> getNext(){
            if(iteratorY == null || !iteratorY.hasNext()){
                if(!iteratorX.hasNext()){
                    return null;
                }
                Map.Entry<T, Map<T, Number>> entry = iteratorX.next();
                x = entry.getKey();
                iteratorY = entry.getValue().entrySet().iterator();
            }

            Map.Entry<T, Number> entry = iteratorY.next();
            return new Table.Entry<T>(x, entry.getKey(), entry.getValue());
        }
    }
}
